﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using UCS.ArtificialInteligence.AI;

namespace UCS.ArtificialInteligence.NumbersProblem.Model
{
    public class NumbersBoard : IProblemState
    {
        #region Constants

        private const int TOTAL_LINES = 3;
        private const int TOTAL_COLUMNS = 3;

        #endregion

        #region Private Members

        private BoardPosition[,] _board;

        #endregion

        #region Attributes and Properties

        private List<ProblemStateBorder> _borders;
        public IEnumerable<ProblemStateBorder> Borders
        {
            get
            {
                if (_borders == null)
                    this.CreateBorders();

                foreach (var border in _borders)
                    yield return border;
            }
        }

        private int? _heuristicValue = null;
        public int HeuristicValue
        {
            get
            {
                if (_heuristicValue == null)
                    this.CalculateHeuristicValue();

                return _heuristicValue.GetValueOrDefault();
            }
        }

        private IProblemState _parent;
        public IProblemState Parent
        {
            get { return _parent; }
            set { _parent = value; }
        }

        #endregion

        #region Constructors

        public NumbersBoard()
        {
            _board = new BoardPosition[TOTAL_LINES, TOTAL_COLUMNS];
        }

        public NumbersBoard(int[,] board)
        {
            CreateBoard(board);
        }

        #endregion

        #region Public Methods

        public bool IsCorrect()
        {
            for (int i = 0; i < TOTAL_LINES; i++)
            {
                for (int j = 0; j < TOTAL_COLUMNS; j++)
                {
                    if (_board[i, j].Value == 0)
                        continue;

                    int finalI = (_board[i, j].Value - 1) / TOTAL_COLUMNS;
                    int finalJ = (_board[i, j].Value - 1) % TOTAL_COLUMNS;

                    if (finalI != i || finalJ != j)
                        return false;
                }
            }

            return true;
        }

        #endregion

        #region Private Methods

        private void CreateBoard(int[,] board)
        {
            _board = new BoardPosition[TOTAL_LINES, TOTAL_COLUMNS];

            for (int i = 0; i < TOTAL_LINES; i++)
                for (int j = 0; j < TOTAL_COLUMNS; j++)
                    _board[i, j] = new BoardPosition(i, j, board[i, j]);
        }

        private void CreateBorders()
        {
            BoardPosition blankPosition = null;
            for (int i = 0; i < TOTAL_LINES && blankPosition == null; i++)
            {
                for (int j = 0; j < TOTAL_COLUMNS && blankPosition == null; j++)
                {
                    if (_board[i, j].Value == 0)
                        blankPosition = _board[i, j];
                }
            }

            _borders = new List<ProblemStateBorder>();

            Action<int, int> addBorder = (i, j) =>
                {
                    BoardPosition pos = _board[i, j];
                    NumbersBoard borderState = this.Clone(pos);
                    borderState.Parent = this;
                    _borders.Add(new ProblemStateBorder(borderState, 1));
                };

            if (blankPosition.I > 0)
                addBorder(blankPosition.I - 1, blankPosition.J);
            if (blankPosition.I < TOTAL_LINES - 1)
                addBorder(blankPosition.I + 1, blankPosition.J);
            if (blankPosition.J > 0)
                addBorder(blankPosition.I, blankPosition.J - 1);
            if (blankPosition.J < TOTAL_COLUMNS - 1)
                addBorder(blankPosition.I, blankPosition.J + 1);
        }

        private void CalculateHeuristicValue()
        {
            int sum = 0;

            for (int i = 0; i < TOTAL_LINES; i++)
                for (int j = 0; j < TOTAL_COLUMNS; j++)
                    sum += this.FindDistance(_board[i, j]);

            _heuristicValue = sum;
        }

        private int FindDistance(BoardPosition currentPosition)
        {
            int finalI = (currentPosition.Value - 1) / TOTAL_COLUMNS;
            int finalJ = (currentPosition.Value - 1) % TOTAL_COLUMNS;

            if (currentPosition.Value == 0)
            {
                finalI = TOTAL_LINES - 1;
                finalJ = TOTAL_COLUMNS - 1;
            }
            else
            {
                finalI = (currentPosition.Value - 1) / TOTAL_COLUMNS;
                finalJ = (currentPosition.Value - 1) % TOTAL_COLUMNS;
            }

            return Math.Abs(currentPosition.I - finalI) + Math.Abs(currentPosition.J - finalJ);
        }

        private NumbersBoard Clone(BoardPosition movement)
        {
            NumbersBoard board = new NumbersBoard();
            BoardPosition blankPosition = null;

            for (int i = 0; i < TOTAL_LINES; i++)
                for (int j = 0; j < TOTAL_COLUMNS; j++)
                {
                    board._board[i, j] = _board[i, j].Clone() as BoardPosition;

                    if (board._board[i, j].Value == 0)
                        blankPosition = board._board[i, j];
                }

            BoardPosition movementPosition = board._board[movement.I, movement.J];

            blankPosition.Value = movementPosition.Value;
            movementPosition.Value = 0;

            return board;
        }

        #endregion

        #region Overriden Methods

        public override string ToString()
        {
            String breakLine = "\n";
            String s = "";
            s += " ----------- " + breakLine;
            for (int i = 0; i < TOTAL_LINES; i++)
            {
                for (int j = 0; j < TOTAL_COLUMNS; j++)
                {
                    s += "| " + (_board[i, j].Value == 0 ? " " : _board[i, j].Value.ToString()) + " ";
                }
                s += "|" + breakLine;
                s += " ----------- " + breakLine;
            }

            return s;
        }

        public override bool Equals(object obj)
        {
            NumbersBoard test = obj as NumbersBoard;

            for (int i = 0; i < TOTAL_LINES; i++)
            {
                for (int j = 0; j < TOTAL_COLUMNS; j++)
                {
                    if (_board[i, j].Value != test._board[i, j].Value)
                        return false;
                }
            }

            return true;
        }

        #endregion
    }
}
